// ----------------------------------------
// Filename: Semaphore.c
// Description: The code that implements a semaphore
// Author: Jon Maloney
// Date: 9-May-2012
//

//************************************************************
//                    Include .h files
//************************************************************
#include "binary_Semaphore.h"
#include "thread.h"
#include "scheduler.h"
#include <stdlib.h>
//************************************************************
//                    Include external references
//************************************************************

//************************************************************
//                    Define Statements
//************************************************************

//************************************************************
//                    Struct, Union, enum Statements
//************************************************************

//************************************************************
//                    Global variables and constants
//************************************************************

//************************************************************
//                    Prototype Private Functions
//************************************************************

//************************************************************
//                    Implementation
//************************************************************


// ----------------------------------------
// sem_Create
// 
// Creates a new semaphore
// Input:
//	pdTRUE: 	During creation semaphore_Wait is called and the 
//					task creating the semaphore is waiting on the semaphroe
//	pdFALSE:	There is no tasks waiting on the semaphore.
// Output:
//  sem_Handle: A pointer to the semaphore created.
//					If the pointer is NULL then the semaphore
//					was not created.
// Conditions:
// 	none
bsem_Handle sem_Create( BOOL initVal )
{
	bsem_t * sem;
	
	//Allocates some heap memory to the semaphore
	sem = (bsem_t *)malloc(sizeof(bsem_t));

	if(sem == NULL)
		return NULL;
	
	//Initialise the semaphore
	sem->taskUsingSemPriority = 0;
	sem->waitingTaskNode = NULL;
	sem->taskUsingSemNode = NULL;

	if(initVal == pdTRUE)
		Semaphore_Wait((bsem_Handle)sem, 0);

	return (bsem_Handle)sem;
}

// ----------------------------------------
// sem_Delete
// 
// Deletes a semaphore
// Input:
//	Semaphore: 	A handle to the semaphore that is to be deleted.
// Output:
//  none
// Conditions:
// 	none
void sem_Delete(bsem_Handle Semaphore)
{
	Semaphore->taskUsingSemPriority = 0;
	Semaphore->waitingTaskNode = NULL;
	Semaphore->taskUsingSemNode = NULL;
	
	free(Semaphore);
	Semaphore = NULL;
}

// ----------------------------------------
// Semaphore_Take
// 
// Gives a task access to a semaphore if the
//	semaphore is available
//	
// Input:
// 	Semaphore: 	A handle to the semaphore that you are trying to take.
//	MaxWaitTime:The maximum time that the thread will wait on the semaphore. 
// Output:
//  pdTRUE: if a semaphore is obtained.
//	pdFALSE:if the semaphore cannot be obtained.
// Conditions:
// 	none
BOOL Semaphore_Wait(bsem_Handle Semaphore, kernelTickType MaxWaitTime)
{
	linkedList_Handle taskNode;
	TCB * task;
	ENTER_CRITICAL();
	//Get a reference to the currently running task
	taskNode = Scheduler_GetCurrentlyRunningTaskNode();
	task = Scheduler_GetCurrentlyRunningTask();
	
	if(Semaphore->taskUsingSemNode == NULL)	//If the semaphore has is available
	{	
		Semaphore->taskUsingSemNode = taskNode;		
		Semaphore->taskUsingSemPriority = task->Priority;
		//No longer in a critical section
		EXIT_CRITICAL();	
		
		//Tell the thread they have the semaphore
		return pdTRUE;	
	}
	else
	{
		Semaphore->waitingTaskNode = taskNode;
		
		/* NOTE must be greater than otherwise when the semaphore is used as a signaling semaphore
			the task waiting on the semaphore will continusously be woken. 

			This is because when the semaphore is used as a signalling semaphore one task is both
			using the semaphore and waiting for the semaphore.

			The other task in the equation will simply post to the semaphore to tell the waiting task
			to wake.*/
		if(task->Priority > Semaphore->taskUsingSemPriority)
		{
			//The task using the semaphore is about to be promoted.
			//	Track this promotion in the semaphore.
			Semaphore->taskUsingSemPriority = task->Priority;
			
			//Must exit critical section before yeilding
			EXIT_CRITICAL();
			
			//At this point the scheduler is likely to context switch and give the CPU to the 
			//	task using the semaphore.
			//
			//The task using the semaphore must temporarily inherit the priority of the task  
			//	waiting on the semaphore so priority inversion does not occur.
			Scheduler_TemporarilyPromoteThread(Semaphore->taskUsingSemNode, task->Priority);	
		}
		else//Priotiry inheritance does not need to occur
		{
			//Must exit critical section before yeilding
			EXIT_CRITICAL();

			//The semaphore is not available wait for it to become available
			Scheduler_Delay( MaxWaitTime );
		}	
		
		/*
			At this point we have either:
				1. Returned after priority inheritance has occured.
				2. Returned after waiting for MaxWaitTime
				3. Returned after Semaphore Signal has been called
		*/
		ENTER_CRITICAL();
		
		//The task is no longer waiting for the semaphore
		Semaphore->waitingTaskNode = NULL;
		
		//After waiting for max wait time check one last time to see if the semaphore is available
		if(Semaphore->taskUsingSemNode == NULL)	
		{
			Semaphore->taskUsingSemNode = taskNode;		
			
			//Update the task using the semaphore's priority
			Semaphore->taskUsingSemPriority = task->Priority;
			
			//No longer in a critical section
			EXIT_CRITICAL();	
			
			//Tell the thread they have the semaphore
			return pdTRUE;
		}
	}		
	
	EXIT_CRITICAL();
	
	//Semaphore MaxWaitTime has expired return without the semaphore
	return pdFALSE;			
}

// ----------------------------------------
// Semaphore_Signal
// 
// Signals any thread waiting on the semaphore
//	that the semaphore is now available
// Input:
// 	Semaphore: 	A handle to the semaphore that you are trying to release a token to.
// Output:
//	none
// Conditions:
// 	none
void Semaphore_Signal(bsem_Handle Sem)
{
	if(Sem == NULL)
		return;
	
	TCB * task = (TCB *)Sem->taskUsingSemNode->Item; 
	
	ENTER_CRITICAL();
	
	//If there is a task waiting on the semaphore
	if(Sem->waitingTaskNode != NULL)
	{
		//Add this task to the ready queue
		(void)Scheduler_MoveTaskNodeToReadyQueue(Sem->waitingTaskNode);
		
		//if the task using the semaphore was promoted to a higher priority
		//	it now needs to be removed from the ready queue.
		if(task->Priority < Sem->taskUsingSemPriority)
			Scheduler_DemoteTask(Sem->taskUsingSemNode);
		
		//The task that just signaled the semaphore is no longer using the semaphore
		//	the waiting will start using the semaphore when it is woken.
		//
		//	We will update the taskUsingSemNode when the task has woken.
		Sem->taskUsingSemNode = NULL;

		//There is no longer a task waiting on the semaphore
		Sem->waitingTaskNode = NULL;

		//If the task that was waiting on the semaphore has a higher priority than the 
		//	task that just signaled the semaphore
		task = (TCB *)Sem->waitingTaskNode->Item; 
		if(task->Priority >= Sem->taskUsingSemPriority)
		{
			EXIT_CRITICAL();
			//A context switch needs to be performed
			Scheduler_PreEmptiveContextSwitch();
		}
		else	//No context switch required.
			EXIT_CRITICAL();
	}
	else
	{
		Sem->taskUsingSemPriority = 0;
		Sem->taskUsingSemNode = NULL;
		EXIT_CRITICAL();
	}
}
